package com.app.util.limiter;

import java.util.concurrent.atomic.AtomicInteger;

import com.app.util.limiter.LimitNames.NameItem;

/**
 * 频率限制器（计数器）
 * 
 * @author yao
 */
public class RateLimiter {

	private LimitItem[] limitItems;
	private LimitNames limitNames;

	private int rateSec;

	/**
	 * 构造一个频率限制器
	 * 
	 * @param limitConfig
	 *            配置对象
	 */
	public RateLimiter(LimitNames limitConfig, int rateSec) { 
		this.limitNames = limitConfig;
		this.rateSec = rateSec;
		this.limitItems = new LimitItem[limitConfig.size()];
		for (NameItem nameItem : limitConfig.getAllNameItem()) {
			LimitItem limitItem = new LimitItem();
			this.limitItems[nameItem.getIndex()] = limitItem;
		}
	}

	/**
	 * 计数加1，返回当前数，如果超出限制范围则返回-1，如果未获得name对应的计数器则返回-2
	 * 
	 * @param name
	 *            计数项
	 * @return -2 计数项不存在； -1 超出上限；n 当前次数
	 */
	public int incr(String name, LimitMaxs limitMaxs) {
		LimitItem limitItem = getLimitItem(name);
		if (null == limitItem) {
			return -2;
		}
		if (limitItem.count.get() >= limitMaxs.getMax(name)) {
			return -1;
		}
		return limitItem.count.addAndGet(1);
	}

	/**
	 * 得到当前计数
	 * 
	 * @param name
	 *            计数项
	 * @return
	 */
	public int get(String name) {
		LimitItem limitItem = getLimitItem(name);
		return limitItem.count.get();
	}

	private LimitItem getLimitItem(String name) {
		NameItem nameItem = limitNames.getNameItem(name);
		if (null == nameItem) {
			return null;
		}
		LimitItem limitItem = limitItems[nameItem.getIndex()];
		long newTime = (System.currentTimeMillis() / rateSec / 1000);
		if (newTime > limitItem.time) {
			synchronized (limitItem) {
				if (newTime > limitItem.time) {
					limitItem.time = newTime;
					limitItem.count.set(0);
				}
			}
		}
		return limitItem;
	}

	public class LimitItem {

		private final AtomicInteger count;
		private long time;

		private LimitItem() {
			this.count = new AtomicInteger(0);
			this.time = System.currentTimeMillis() / 1000 / rateSec;
		}

		public AtomicInteger getCount() {
			return count;
		}

	}
}
